using System.Reflection;
using System.Collections.Generic;
using System;
using Godot;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Box;

public static class ProcessTreeEditorHelper {
    public static void IterNodes(ProcessNodeControl node,Action<ProcessNodeControl> f,Dictionary<ProcessNodeControl,ProcessNodeControl> iter_map = null) {
        if(iter_map == null) {
            iter_map = new Dictionary<ProcessNodeControl, ProcessNodeControl>();
        }
        if(iter_map.ContainsKey(node)) return;
        f(node);
        iter_map[node] = node;
        foreach(var port in node.OutputPorts.Values) {
            foreach(var cnode in port.Connects) {
                IterNodes(cnode.Node,f,iter_map);
            }
        }
    }

    public static string GenerateCSCode(ProcessNodeControl root,string namespace_name,string class_name) {
        string code = "";

          

        return code;
    }

    public static string GenerateCode(ProcessNodeControl root,string namespace_name,string class_name) {
        Dictionary<ProcessNodeControl,string> node_vars = new Dictionary<ProcessNodeControl, string>();
        string generate_node_code = "";
        int var_count = 0;

        //初始化节点
        IterNodes(root,node => {
            string var_name = $"___pnode_{var_count}";
            node_vars[node] = var_name;
            string node_init_code = $"var {var_name} = new {node.ProcessTreeNodeClassName}(); ";
            node_init_code += node.GenerateInitCodeString(var_name);
            
            string label = (string)(node.Properties["Label"].PValue);
            if(label != "") {
                node_init_code += $"Tree.LabelNodes[{label}] = {var_name}; ";
            }

            node_init_code += $"{var_name}.Tree = Tree;";

            generate_node_code += node_init_code;
            var_count++;
        });

        generate_node_code += " ";
        //连接节点
        foreach(var item in node_vars) {
            string var_name = item.Value;
            var node = item.Key;
            string connect_code = "";
            foreach(var port_item in node.OutputPorts) {
                string port_name = port_item.Key;
                ProcessNodeControlPort port = port_item.Value;
                foreach(var connect_port in port.Connects) {
                    connect_code += $" {var_name}.Connect(\"{port.Name}\",{node_vars[connect_port.Node]},\"{connect_port.Name}\");";
                }
            }
            generate_node_code += connect_code;
        }

        string code = namespace_name == "" ? 
$@"
using Box.Base;
using Godot;

public class {class_name}_PTree : Node {{
    public {class_name} self;
    public ProcessTree Tree = new ProcessTree();

    public override void _Ready() {{
        self = GetParent();
{generate_node_code}
    }}
}}
"
:

$@"
using Box.Base;
using Godot;

{namespace_name} {{
    public class {class_name}_PTree : Node {{
        public {class_name} self;
        public ProcessTree Tree = new ProcessTree();

        public override void _Ready() {{
            self = GetParent();
{generate_node_code}
        }}
    }}
}}
";
        var tree = CSharpSyntaxTree.ParseText(code);
        var tree_root = tree.GetRoot().NormalizeWhitespace();
        return  tree_root.ToFullString();
    }

    public static string ToJsonItemValue(IProperty property) {
        if(property is NumberPropertyControl) {
            return property.PValue.ToString();
        }

        if(property is BooleanPropertyControl) {
            return property.ToString();
        }

        if(property is StringPropertyControl || property is CodePropertyControl) {
            if(property.ToString() == "") return "\"\"";
            return "\"" + property.ToString().Replace("\n","\\n").Replace("\\\"","\\\\\"").Replace("\"","\\\"") + "\"";
        }

        return property.ToString();
    } 

    public struct ProcessTreeJsonNodeItem {
        public string node_name;
        public string name;
        public string type;
        public float x;
        public float y;
        public Dictionary<string,object> properties;
        public List<List<string>> connects;

        public ProcessTreeJsonNodeItem() {
            x = 0;
            y = 0;
            type = "";
            name = "";
            node_name = "";
            properties = new Dictionary<string, object>();
            connects = new List<List<string>>();
        }
    }

    public struct ProcessTreeJsonStruct {
        public string tree_name;
        public List<ProcessTreeJsonNodeItem> nodes;
    }

    const int NODE_ROOT = 0;
    const int NODE_ACTION = 1;
    const int NODE_IA = 2;
    const int NODE_PA = 3;
    const int NODE_SA = 4;

    static Dictionary<string,Type> ProcessTreeNodeTypes = null;
    static Type ProcessTreeNodeControlType = typeof(ProcessNodeControl);
    static Dictionary<string,string> Nodes = null;

    static ProcessNodeControl CreateNode(string type) {
        if(Nodes == null) {
            Nodes = new Dictionary<string, string>();
            var nodes = DirAccess.Open("res://source/process_tree_editor/node");
            var files = nodes.GetFiles();
            foreach(var file_name in files) {
                if(file_name.Find(".json") != -1) {
                    ProcessNodeControl _control = new ProcessNodeControl();
                    _control.Setting($"res://source/process_tree_editor/node/{file_name}");
                    Nodes[_control.NodeType] = $"res://source/process_tree_editor/node/{file_name}";
                }
            }
        }
        string setting_file = Nodes[type];
        ProcessNodeControl control = new ProcessNodeControl();
        control.Setting(setting_file);
        return control;
    }

    static string GetNodeType(ProcessNodeControl node) {
        if(ProcessTreeNodeTypes == null) {
            ProcessTreeNodeTypes = new Dictionary<string, Type>();
            Assembly assembly = Assembly.GetExecutingAssembly();
            foreach(Type _type in assembly.GetTypes()) {
                if(_type.IsAssignableTo(ProcessTreeNodeControlType)) {
                    ProcessTreeNodeTypes[_type.Name] = _type;
                }
            }
        }
        return node.GetType().Name;
    }

    public static string ToJson(ProcessTreeGraphEditor edit) {
        ProcessTreeJsonStruct json_struct = new ProcessTreeJsonStruct();
        List<ProcessTreeJsonNodeItem> nodes = new List<ProcessTreeJsonNodeItem>();
        json_struct.nodes = nodes;
        json_struct.tree_name = edit.Name;

        Dictionary<ProcessNodeControl,string> node_keys = new Dictionary<ProcessNodeControl, string>();
        int key_count = 0;

        foreach(Node _node in edit.GetChildren()) {
            if(_node is ProcessNodeControl) {
                ProcessNodeControl node = _node as ProcessNodeControl;
                string key = $"node_{key_count}";
                if(node.NodeType == "root") {
                    key = "Root";
                }
                node_keys[node] = key;

                key_count++;
            }
        }

        foreach(var item in node_keys) {
            var node = item.Key;
            var key = item.Value;
            ProcessTreeJsonNodeItem node_struct = new ProcessTreeJsonNodeItem();
            node_struct.node_name = key;
            node_struct.type = node.NodeType;
            node_struct.x = node.PositionOffset.X;
            node_struct.y = node.PositionOffset.Y;
            node_struct.name = node.NameEdit.Text;

            int i = 0;
            foreach(Node _property in node.PropertyControls) {
                IProperty property = _property as IProperty;
                node_struct.properties.Add(property.PName,property.PValue);
                i++;
            }
            List<string> connect_items = new List<string>();
            foreach(var port_item in node.OutputPorts) {
                string port_name = port_item.Key;
                ProcessNodeControlPort port = port_item.Value;
                foreach(var connect_port in port.Connects) {
                    node_struct.connects.Add(new List<string>{port.Name,node_keys[connect_port.Node],connect_port.Name});
                }
            }

            nodes.Add(node_struct);
        }

        return JsonConvert.SerializeObject(json_struct);
    }

    public static ProcessTreeGraphEditor ReadJson(string json_str, ProcessTreeGraphEditor edit = null) {
        ProcessTreeJsonStruct json_struct = JsonConvert.DeserializeObject<ProcessTreeJsonStruct>(json_str);

        if(edit == null) {
            var scene = GD.Load<PackedScene>("res://source/process_tree_editor/process_tree_graph_editor/ProcessTreeGraphEditor.tscn");
            edit = scene.Instantiate<ProcessTreeGraphEditor>();
            edit.Name = json_struct.tree_name;
        }

        Dictionary<string,ProcessNodeControl> nodes = new Dictionary<string, ProcessNodeControl>();

        edit.CleraAllNodes();

        foreach(var node_struct in json_struct.nodes) {
            var node = CreateNode(node_struct.type);
            foreach(var item in node_struct.properties) {
                IProperty property = node.Properties[item.Key];
                property.PValue = item.Value;
            }
            nodes[node_struct.node_name] = node;
            node.Name = node_struct.node_name;
            node.NameEdit.Text = node_struct.name;

            node.PositionOffset = new Vector2(node_struct.x,node_struct.y);

            if(node.NodeType != "root") {
                edit.AddChild(node);
            }
        }

        foreach(var node_struct in json_struct.nodes) {
            var node = nodes[node_struct.node_name];

            foreach(var connect in node_struct.connects) {
                string from_port = connect[0];
                string to_node_name = connect[1];
                string to_port = connect[2];

                long from_slot = node.OutputPorts[from_port].SlotID;
                ProcessNodeControl to_node = nodes[to_node_name];
                long to_slot = to_node.InputPorts[to_port].SlotID;
                edit._onConnectRequest(node.Name,from_slot,to_node_name,to_slot);
            }
        }

        return edit;
    }
}